﻿// -----------------------------------------------------------------------
// <copyright file="AbstractFlow.cs" company="Microsoft">
// TODO: Update copyright text.
// </copyright>
// -----------------------------------------------------------------------

namespace TouchControls.MenuFlows
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows;
using System.Windows.Shapes;
    using System.Windows.Data;
    using System.Windows.Media;

    /// <summary>
    /// TODO: Update summary.
    /// </summary>
    abstract class AbstractFlow : ITouchMenuFlow
    {
        protected double TotalSize { get; private set; }
        public List<TouchMenuItem> Items { get; private set; }

        protected double ItemSize { get; private set; }
        protected Rect ButtonRect { get; private set; }
        protected Size AdornmentSize { get; private set; }

        public TouchMenuPanel Panel { get; private set; }

        public AbstractFlow(TouchMenuPanel pnl)
        {
            this.Panel = pnl;
        }

        public virtual Size Measure(System.Windows.Size availableSize)
        {
            this.InitMeasures(); 
            this.PrepareLines(availableSize);
            foreach (var touchMenuItem in Items)
            {
                touchMenuItem.Measure(availableSize);
            }            
            return Panel.Adornment.RenderSize;
        }

        private void PrepareLines(Size availableSize)
        {
            foreach (var child in Items)
            {
                child.Measure(availableSize);
                this.AddLine();
            }
            this.AddLine();
            this.AddLine();
        }

        private void AddLine()
        {
            var line = new Line();
            line.StrokeThickness = Panel.LinesThickness;
            line.StrokeDashCap = PenLineCap.Square;
            //line.StrokeDashArray.Add(1);
            //line.StrokeDashArray.Add(2);
            line.Stroke = Panel.CurrentItem.Foreground;
            this.Panel.CurrentItem.LineCanvas.Children.Add(line);
            SetLineBinding(Panel.CurrentItem, line);
        }

        public virtual Size Arrange(System.Windows.Size availableSize)
        {
            this.InitMeasures();
            return Panel.Adornment.RenderSize;
        }

        protected void InitMeasures()
        {
            if (Panel.CurrentItem.ButtonRect == Rect.Empty)
            {
                Panel.CurrentItem.InvalidateMeasure();
            }
            ButtonRect = Panel.CurrentItem.ButtonRect;
            ItemSize = Panel.CurrentItem.MenuTileSize.Value;
            AdornmentSize = Panel.Adornment.RenderSize;
            Items =
                Panel.Children.Cast<UIElement>().Where(e => e.Visibility == Visibility.Visible && e is TouchMenuItem).
                    Cast<TouchMenuItem>().ToList();

            TotalSize = Items.Count * ItemSize + ((Items.Count - 1) * Panel.Space);            
        }

        public double ChildItemLineLength
        {
            get
            {
                return Panel.LinesCoeff * Panel.Distance + (Panel.LinesThickness / 2);
            }
        }

        public double ParentItemLineLength
        {
            get
            {
                return (1.0 - Panel.LinesCoeff) * Panel.Distance - (Panel.LinesThickness / 2);
            }
        }

        protected abstract void SetLine(Rect itemPos, Line line);

        protected abstract void EndLines();

        private void SetLineBinding(TouchMenuItem item, Line line)
        {
            Binding bd = new Binding();
            bd.Source = item.ItemButton;
            bd.Path = new PropertyPath("Opacity");
            line.SetBinding(UIElement.OpacityProperty, bd);
        }

        static ITouchMenuFlow GetInstanceFor(TouchMenuPanel pnl)
        {            
            if (pnl.ItemsContainer.Flow == MenuFlow.Left)
                return new FlowLeft(pnl);
            if (pnl.ItemsContainer.Flow == MenuFlow.Top)
                return new FlowTop(pnl);            
            
            if (pnl.ItemsContainer.Flow == MenuFlow.Right)
                return new FlowRight(pnl);

            if (pnl.ItemsContainer.Flow == MenuFlow.Bottom)
                return new FlowBottom(pnl);

            return null;
        }

        public static ITouchMenuFlow Get(TouchMenuPanel pnl)
        {
            if (pnl.ItemsContainer.Flow == MenuFlow.Unknown)
                DecideMenuFlow(pnl);

            return GetInstanceFor(pnl);
        }

        private static void DecideMenuFlow(TouchMenuPanel pnl)
        {
            //var position = pnl.ItemsContainer.ParentMenu.TransformToVisual(pnl.Adornment).Transform(new Point());
            var checkedSize = pnl.Depth * pnl.ItemsContainer.MenuTileSize.Value + pnl.Depth * pnl.Space;
            checkedSize = checkedSize + pnl.ItemsContainer.MenuTileSize.Value;

            
            if (CheckClosest(pnl, checkedSize))
                return;

            if (AllowFlowTop(pnl, checkedSize))
            {
                pnl.ItemsContainer.Flow = MenuFlow.Top;
                return;
            }

            if (AllowFlowLeft(pnl, checkedSize))
            {
                pnl.ItemsContainer.Flow = MenuFlow.Left;
                return;
            }
            
            if (AllowFlowRight(pnl, checkedSize))
            {
                pnl.ItemsContainer.Flow = MenuFlow.Right;
                return;
            }

            if (AllowFlowBottom(pnl, checkedSize))
            {
                pnl.ItemsContainer.Flow = MenuFlow.Bottom;
                return;
            }
            
            pnl.ItemsContainer.Flow = MenuFlow.None;
        }

        private static bool AllowFlowBottom(TouchMenuPanel pnl, double checkedSize)
        {
            return pnl.ItemsContainer.ButtonRect.Y + pnl.ItemsContainer.ButtonRect.Height + checkedSize < pnl.Adornment.RenderSize.Height;
        }

        private static bool AllowFlowTop(TouchMenuPanel pnl, double checkedSize)
        {
            return pnl.ItemsContainer.ButtonRect.Y - checkedSize > 0;
        }

        private static bool AllowFlowRight(TouchMenuPanel pnl, double checkedSize)
        {
            return pnl.ItemsContainer.ButtonRect.X + pnl.ItemsContainer.ButtonRect.Width + checkedSize < pnl.Adornment.RenderSize.Width;
        }

        private static bool AllowFlowLeft(TouchMenuPanel pnl, double checkedSize)
        {
            return pnl.ItemsContainer.ButtonRect.X - checkedSize > 0;
        }

        private static bool CheckClosest(TouchMenuPanel pnl, double checkedSize)
        {
            var _distanceToLeft = pnl.ItemsContainer.ButtonRect.X;
            var _distanceToRight = pnl.Adornment.RenderSize.Width - (pnl.ItemsContainer.ButtonRect.X + pnl.ItemsContainer.ButtonRect.Width);
            var _distanceToTop = pnl.ItemsContainer.ButtonRect.Y;
            var _distanceToBottom = pnl.Adornment.RenderSize.Height - (pnl.ItemsContainer.ButtonRect.Y + pnl.ItemsContainer.ButtonRect.Height);

            if (CheckClosestElement(pnl, _distanceToRight,  checkedSize, MenuFlow.Left,   AllowFlowLeft)) return true;
            if (CheckClosestElement(pnl, _distanceToLeft,   checkedSize, MenuFlow.Right,  AllowFlowRight)) return true;
            if (CheckClosestElement(pnl, _distanceToTop,    checkedSize, MenuFlow.Bottom, AllowFlowBottom)) return true;
            if (CheckClosestElement(pnl, _distanceToBottom, checkedSize, MenuFlow.Top,    AllowFlowTop)) return true;
            
            return false;
        }

        private static bool CheckClosestElement(TouchMenuPanel pnl, double distanceToCheck, double checkedSize, MenuFlow result, Func<TouchMenuPanel, double, bool> check )
        {
            if (distanceToCheck - pnl.ItemsContainer.MenuTileSize < 0 && check(pnl, checkedSize))
            {
                pnl.ItemsContainer.Flow = result;
                return true;
            }

            return false;
        }
    }
}
